gusucode.com > VC++ CStatic文字滚动特效-源码程序 > VC++ CStatic文字滚动特效-源码程序/code/CStatic文字滚动a/CCreditsCtrl_demo/CreditsCtrl.cpp

    // CreditsCtrl.cpp : implementation file
// Download by http://www.NewXing.com

#include "stdafx.h"
#include "CreditsDlg.h"
#include "CreditsCtrl.h"

#include <afxtempl.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define CCREDITCTRL_TIMER1	100

// use transparent BitBlts if supported? ( UNTESTED!! )
//#define CCREDITCTRL_USE_TRANSPARENT_BITBLT

// stuff that _should_ have been defined in some header :-/
#ifndef C1_TRANSPARENT
    #define C1_TRANSPARENT 0x0001
#endif 
#ifndef CAPS1
    #define CAPS1 94
#endif 
#ifndef NEWTRANSPARENT
    #define NEWTRANSPARENT 3
#endif

/////////////////////////////////////////////////////////////////////////////
// CCreditsCtrl
LPCTSTR CCreditsCtrl::m_lpszClassName = NULL;

CCreditsCtrl::CCreditsCtrl()
{
	m_nTimerSpeed = 40;
	m_nCurBitmapOffset = 0;
	m_crInternalTransparentColor = RGB(255,0,255);
	m_pBackgroundPaint = CCreditsCtrl::DrawBackground;
	m_dwBackgroundPaintLParam = GetSysColor(COLOR_BTNFACE);//(DWORD)m_crBackgroundColor;
	m_hLinkCursor = NULL;
	m_hDefaultCursor = NULL;
	m_bCanScroll = TRUE;
	m_bIsScrolling = FALSE;
}

CCreditsCtrl::~CCreditsCtrl()
{
}


BEGIN_MESSAGE_MAP(CCreditsCtrl, CWnd)
	//{{AFX_MSG_MAP(CCreditsCtrl)
	ON_WM_PAINT()
	ON_WM_TIMER()
	ON_WM_SIZE()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


BOOL CCreditsCtrl::Create(DWORD dwExStyle, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, UINT nDefaultCursorID, UINT nLinkCursorID)
{
	m_hDefaultCursor = nDefaultCursorID == 0 ? NULL : AfxGetApp()->LoadCursor(nDefaultCursorID);
	if(nLinkCursorID == 0)
		SetDefaultLinkCursor();
	else
		m_hLinkCursor = AfxGetApp()->LoadCursor(nLinkCursorID);

	// register window class & create CWnd object
	if (m_lpszClassName == NULL)
		m_lpszClassName = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW);

	BOOL bResult = CreateEx(dwExStyle, m_lpszClassName, _T(""), dwStyle,
		rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
		pParentWnd->GetSafeHwnd(), (HMENU)nID, NULL );

	// start scrolling
	if(bResult)
		SetTimer(CCREDITCTRL_TIMER1,m_nTimerSpeed*10,NULL);
	
	return bResult;
}

BOOL CCreditsCtrl::Create(DWORD dwExStyle, DWORD dwStyle, UINT nPlaceholderID, CWnd* pParentWnd, UINT nID, UINT nDefaultCursorID, UINT nLinkCursorID)
{
	// get rect from placeholder and call create with the found rect
	RECT rect;
	pParentWnd->GetDlgItem(nPlaceholderID)->GetWindowRect(&rect);
	pParentWnd->ScreenToClient(&rect);
	return Create(dwExStyle, dwStyle, rect, pParentWnd, nID, nDefaultCursorID, nLinkCursorID);
}

/////////////////////////////////////////////////////////////////////////////
// CCreditsCtrl message handlers

void CCreditsCtrl::OnPaint() 
{
	static BOOL bFirstDraw = TRUE;

	CPaintDC dc(this); // device context for painting

	// init memory DC
	CDC memDC;
	memDC.CreateCompatibleDC(&dc);
	int nMemDCSave = memDC.SaveDC();
	CBitmap memBmp;
	memBmp.CreateCompatibleBitmap(&dc,m_rcClient.Width(),m_rcClient.Height());
	memDC.SelectObject(&memBmp);

	// draw backgorund
	if(m_pBackgroundPaint!=NULL)
		(*m_pBackgroundPaint)(&memDC,m_rcClient,m_bIsScrolling||bFirstDraw,m_dwBackgroundPaintLParam);

	// calculate hot rectagle position and save background at that location
	CDC hotBgDC;
	CBitmap hotbgBmp,*pOldHBgBmp;
	CRect rcHotRect;
	if(m_rcHotRect != CRect(0,0,0,0))
	{
		hotBgDC.CreateCompatibleDC(&memDC);
		hotbgBmp.CreateCompatibleBitmap(&memDC,m_rcHotRect.Width(),m_rcHotRect.Height());
		pOldHBgBmp = hotBgDC.SelectObject(&hotbgBmp);

		if(m_nBitmapHeight <= m_rcClient.bottom)
			rcHotRect = m_rcHotRect;
		else if(m_nBitmapHeight-m_nCurBitmapOffset+m_rcHotRect.top < m_rcClient.bottom)
			rcHotRect.SetRect(m_rcHotRect.left,m_nBitmapHeight-m_nCurBitmapOffset+m_rcHotRect.top,m_rcHotRect.right,m_nBitmapHeight-m_nCurBitmapOffset+m_rcHotRect.bottom);
		else
			rcHotRect.SetRect(m_rcHotRect.left,m_rcHotRect.top-m_nCurBitmapOffset,m_rcHotRect.right,m_rcHotRect.bottom-m_nCurBitmapOffset);

		hotBgDC.BitBlt(0,0,m_rcHotRect.Width(),m_rcHotRect.Height(),&memDC,rcHotRect.left,rcHotRect.top,SRCCOPY);
	}

	// draw normal bitmap
	if(m_nBitmapHeight <= m_rcClient.bottom)
	{
		CRect rect = m_rcClient;
		rect.bottom = m_nBitmapHeight;
		DrawTransparentBitmap(&m_bmpNormal,&memDC,m_crInternalTransparentColor,rect,rect);
	} else
	{
		DrawTransparentBitmap(&m_bmpNormal,&memDC,m_crInternalTransparentColor,CRect(0,0,m_rcClient.right,min(m_nBitmapHeight-m_nCurBitmapOffset,m_rcClient.bottom)),CRect(0,m_nCurBitmapOffset,0,0/*the two last values are not taken into account by DrawTransparentBitmap anyway*/));
		if(m_nBitmapHeight-m_nCurBitmapOffset < m_rcClient.bottom)
			DrawTransparentBitmap(&m_bmpNormal,&memDC,m_crInternalTransparentColor,CRect(0,m_nBitmapHeight-m_nCurBitmapOffset,m_rcClient.right,m_rcClient.bottom),CRect(0,0,0,0/*the two last values are not taken into account by DrawTransparentBitmap anyway*/));
	}

	// draw hot rect onto generic background
	if(m_rcHotRect != CRect(0,0,0,0))
	{
		memDC.BitBlt(rcHotRect.left,rcHotRect.top,rcHotRect.Width(),rcHotRect.Height(),&hotBgDC,0,0,SRCCOPY);
		DrawTransparentBitmap(&m_bmpHot,&memDC,m_crInternalTransparentColor,rcHotRect,m_rcHotRect);
		hotBgDC.SelectObject(pOldHBgBmp);
	}
	
	// copy memory DC to screen
	dc.BitBlt(0,0,m_rcClient.Width(),m_rcClient.Height(),&memDC,0,0,SRCCOPY);

	memDC.RestoreDC(nMemDCSave);

	if(bFirstDraw)
		bFirstDraw = FALSE;
}

CString CCreditsCtrl::SetDataString(LPCTSTR lpszNewString)
{
	CString sOldString = m_sData;
	m_sData = lpszNewString;
	if(IsWindow(m_hWnd))
		Initialize();
	return sOldString;
}

CString CCreditsCtrl::SetDataString(UINT nStringResourceID)
{
	CString sOldString = m_sData;
	m_sData.LoadString(nStringResourceID);
	if(IsWindow(m_hWnd))
		Initialize();
	return sOldString;
}

CString CCreditsCtrl::FormatDataString(LPCTSTR lpszFormat, ...)
{
	ASSERT(AfxIsValidString(lpszFormat));
	
	CString sOldString = m_sData; // store old string

	// let CString do the formatting
	va_list argList;
	va_start(argList, lpszFormat);
	m_sData.FormatV(lpszFormat, argList);
	va_end(argList);

	if(IsWindow(m_hWnd)) // Initialize bitmaps if we have already been Create()d
		Initialize();

	return sOldString;
}

CString CCreditsCtrl::FormatDataString(UINT nFormatID, ...)
{
	CString strFormat;
	VERIFY(strFormat.LoadString(nFormatID) != 0); // load resource string
	
	CString sOldString = m_sData; // store old string

	// let CString do the formatting
	va_list argList;
	va_start(argList, nFormatID);
	m_sData.FormatV(strFormat, argList);
	va_end(argList);

	if(IsWindow(m_hWnd)) // Initialize bitmaps if we have already been Create()d
		Initialize();

	return sOldString;
}

CString CCreditsCtrl::GetDataString()
{
	return m_sData;
}

void CCreditsCtrl::SetDefaultLinkCursor()
{
	// following code is taken from Chris Maunders hyperlink control (http://www.codeproject.com) - tnx
	if (m_hLinkCursor == NULL)		// No cursor handle - load our own
	{		
        // Get the windows directory
		CString strWndDir;
		GetWindowsDirectory(strWndDir.GetBuffer(MAX_PATH), MAX_PATH);
		strWndDir.ReleaseBuffer();

		strWndDir += _T("\\winhlp32.exe");
		// This retrieves cursor #106 from winhlp32.exe, which is a hand pointer
		HMODULE hModule = LoadLibrary(strWndDir);
		if (hModule) {
			HCURSOR hHandCursor = ::LoadCursor(hModule, MAKEINTRESOURCE(106));
			if (hHandCursor)
				m_hLinkCursor = CopyCursor(hHandCursor);
		}
		FreeLibrary(hModule);
	}
}

void CCreditsCtrl::OnTimer(UINT nIDEvent) 
{
	if(nIDEvent == CCREDITCTRL_TIMER1)
	{
		if(IsWindowVisible())
		{
			// increment bitmap offset
			if(++m_nCurBitmapOffset > m_nBitmapHeight)
				m_nCurBitmapOffset = 1;

			// update cursor
			CPoint point,pt;
			GetCursorPos(&point);
			pt = point;
			ScreenToClient(&point);
			if(m_rcClient.PtInRect(point) && WindowFromPoint(pt)==this)
			{
				CRect rect;
				int n;
				if((n = HitTest(point)) != -1)
				{
					rect = m_HotRects[n];
					SetCursor(m_hLinkCursor);
				}
				else
				{
					rect = CRect(0,0,0,0);
					SetCursor(m_hDefaultCursor);
				}
				if(rect != m_rcHotRect)
					m_rcHotRect = rect;
			}

			// update window
			Invalidate(FALSE);
			UpdateWindow();

			// set timer
			SetTimer(CCREDITCTRL_TIMER1,m_nTimerSpeed,NULL);
		}
	} else
		CWnd::OnTimer(nIDEvent);
}

void CCreditsCtrl::OnSize(UINT nType, int cx, int cy) 
{
	CWnd::OnSize(nType, cx, cy);
	
	if(IsWindow(m_hWnd))
		GetClientRect(m_rcClient);
	Initialize();
}

void CCreditsCtrl::TransparentBlt(CDC *pSrcDC, CDC* pDestDC,COLORREF crTrans,const CRect& rcDest,const CRect& rcSrc)
{
	int SaveDestDC = pDestDC->SaveDC();
	int SaveSrcDC = pSrcDC->SaveDC();

#ifdef CCREDITCTRL_USE_TRANSPARENT_BITBLT	// use transparent BitBlts if supported?
		// Only attempt this if device supports functionality. ( untested!! )
	if(pDestDC->GetDeviceCaps(CAPS1) & C1_TRANSPARENT)
	{
		// Special transparency background mode
		pDestDC->SetBkMode(NEWTRANSPARENT);
		pDestDC->SetBkColor(crTrans);
		// Actual blt is a simple source copy; transparency is automatic.
		pDestDC->BitBlt(rcDest.left, rcDest.top, rcDest.Width(), rcDest.Height(), pSrcDC, rcSrc.left, rcSrc.top, SRCCOPY);
	} else	// if driver doesn't support transparent BitBlts, do it the hard way
	{
#endif
		// initialize memory DC and monochrome mask DC
		CDC tmpDC, maskDC;
		CBitmap bmpTmp, bmpMask;
		int SaveTmpDC, SaveMaskDC;
		tmpDC.CreateCompatibleDC(pDestDC);
		maskDC.CreateCompatibleDC(pDestDC);
		SaveTmpDC = tmpDC.SaveDC();
		SaveMaskDC = maskDC.SaveDC();
		bmpTmp.CreateCompatibleBitmap(pDestDC,rcDest.Width(),rcDest.Height());
		bmpMask.CreateBitmap(rcDest.Width(),rcDest.Height(),1,1,NULL);
		tmpDC.SelectObject(&bmpTmp);
		maskDC.SelectObject(&bmpMask);

		// copy existing data from destination dc to memory dc
		tmpDC.BitBlt(0,0,rcDest.Width(),rcDest.Height(),pDestDC,rcDest.left,rcDest.top,SRCCOPY);

		// create mask
		pSrcDC->SetBkColor(crTrans);
		maskDC.BitBlt(0,0,rcDest.Width(),rcDest.Height(),pSrcDC,rcSrc.left,rcSrc.top,SRCCOPY);

		// do some BitBlt magic
		tmpDC.SetBkColor(RGB(255,255,255));
		tmpDC.SetTextColor(RGB(0,0,0));
		tmpDC.BitBlt(0,0,rcDest.Width(),rcDest.Height(),pSrcDC,rcSrc.left,rcSrc.top,SRCINVERT);
		tmpDC.BitBlt(0,0,rcDest.Width(),rcDest.Height(),&maskDC,0,0,SRCAND);
		tmpDC.BitBlt(0,0,rcDest.Width(),rcDest.Height(),pSrcDC,rcSrc.left,rcSrc.top,SRCINVERT);
		
		// copy what we have in our memory DC to the destination DC
		pDestDC->BitBlt(rcDest.left,rcDest.top,rcDest.Width(),rcDest.Height(),&tmpDC,0,0,SRCCOPY);

		// clean up
		tmpDC.RestoreDC(SaveTmpDC);
		maskDC.RestoreDC(SaveMaskDC);

#ifdef CCREDITCTRL_USE_TRANSPARENT_BITBLT
	}
#endif
	pDestDC->RestoreDC(SaveDestDC);
	pSrcDC->RestoreDC(SaveSrcDC);
}

void CCreditsCtrl::DrawTransparentBitmap(CBitmap *pBitmap, CDC* pDC,COLORREF crTrans,const CRect& rcDest,const CRect& rcSrc)
{
	int SaveImageDC;

	// initialize image DC
	CDC imageDC;
	imageDC.CreateCompatibleDC(pDC);
	SaveImageDC = imageDC.SaveDC();
	imageDC.SelectObject(pBitmap);

	TransparentBlt(&imageDC,pDC,crTrans,rcDest,rcSrc);

	// clean up
	imageDC.RestoreDC(SaveImageDC);
}

void CCreditsCtrl::Initialize()
{
	//// [Initialize] ///////////////////////////////////////
	//                                                     //
	//  Create bitmaps and calc hot regions from m_sData   //
	//                                                     //
	/////////////////////////////////////////////////////////
	int nMaxHeight = 5000;

	// initialize normal and hot DCs
	CDC *pDC = GetDC();
	CDC normalDC, hotDC;
	normalDC.CreateCompatibleDC(pDC);
	hotDC.CreateCompatibleDC(pDC);
	int nSaveDCNormal = normalDC.SaveDC();
	int nSaveDCHot = hotDC.SaveDC();

	// initialize bitmaps
	if(m_bmpNormal.m_hObject)
		m_bmpNormal.DeleteObject();
	m_bmpNormal.CreateCompatibleBitmap(pDC,m_rcClient.Width(),nMaxHeight);
	if(m_bmpHot.m_hObject)
		m_bmpHot.DeleteObject();
	m_bmpHot.CreateCompatibleBitmap(pDC,m_rcClient.Width(),nMaxHeight);

	// select bitmaps into DCs
	normalDC.SelectObject(&m_bmpNormal);
	hotDC.SelectObject(&m_bmpHot);

	// fill with transparent color
	normalDC.FillSolidRect(0,0,m_rcClient.right,nMaxHeight,m_crInternalTransparentColor);
	hotDC.FillSolidRect(0,0,m_rcClient.right,nMaxHeight,m_crInternalTransparentColor);

	CString sData = m_sData;
	
	// substitute line break tags with newline characters
//	sData.Remove('\n');
//	sData.Replace("<br>","\n");
//	sData.Replace("<p>","\n\n");

	// make sure we get the last line displayed
	sData += '\n';

	// variables used for parsing
	CList<font_attribs,font_attribs&> font_attribs_tree;
	font_attribs fa;
	fa.bBold = FALSE;
	fa.bItalic = FALSE;
	fa.bUnderline = FALSE;
	fa.bStrikeout = FALSE;
	fa.crBkColor = CLR_NONE;
	fa.crColor = RGB(0,0,0);
	fa.nSize = 12;
	strcpy(fa.szName,"Arial");
	font_attribs_tree.AddTail(fa); // default font
	CList<general_attribs,general_attribs&> general_attribs_tree;
	general_attribs ga;
	ga.nAlign = 1;
	ga.nVAlign = 1;
	ga.nMaxWidth = m_rcClient.Width();
	ga.nMaxHeight = nMaxHeight;
	general_attribs_tree.AddTail(ga); // default alignment
	font_attribs link;
	BOOL bInsideTag = FALSE;
	CString sCurTagName;
	CString sCurElement;
	CString sCurOption;
	int nCurHPos = 0;
	int nCurVPos = 0;
	int nCurLineHeight = 0;
	CArray<line_rect,line_rect&> arcLineRects; // list containg information about the elements in the current line. used for vertical alignment of these element at line break.
	BOOL bIsLineEmpty = TRUE;
	BOOL bIsOption = FALSE;
	TCHAR cTmp;
	COLORREF crHrColor;
	int nHrWidth;
	int nHrSize;
	int nHrAlign;
	CString sCurLink;
	COLORREF crBitmap;
	int nBitmapBorder;
	CString sBitmap;

	CDC lineDC;
	lineDC.CreateCompatibleDC(&normalDC);
	CBitmap lineBmp;
	lineBmp.CreateCompatibleBitmap(&normalDC,ga.nMaxWidth,ga.nMaxHeight);
	CBitmap *pOldBmp = lineDC.SelectObject(&lineBmp);

	CDC hover_lineDC;
	hover_lineDC.CreateCompatibleDC(&hotDC);
	CBitmap hover_lineBmp;
	hover_lineBmp.CreateCompatibleBitmap(&hotDC,ga.nMaxWidth,ga.nMaxHeight);
	CBitmap *pOldHoverBmp = hover_lineDC.SelectObject(&hover_lineBmp);

	// main parsing loop... processing character by character
	//  (don't even _try_ to understand what's going on here :)
	for(int i = 0; i < sData.GetLength() && i >= 0; i++)
	{
		if(!bInsideTag)
		{
			if(sData[i] == '<')
			{
				if(sCurElement != "")
				{
					Parse_AppendText(&lineDC,&hover_lineDC,&nCurHPos,&nCurVPos,&nCurLineHeight,&arcLineRects,&general_attribs_tree.GetTail(),&font_attribs_tree.GetTail(),sCurElement, sCurLink, link);
					bIsLineEmpty = FALSE;
				}
				sCurTagName = "";
				sCurElement = "";
				bInsideTag = TRUE;
				continue;
			}
			if(sData[i] == '\n') // line break
			{
				if(bIsLineEmpty) // if line is empty add the height of a space with the current font
				{
					fa = font_attribs_tree.GetTail();
					CFont font;
					font.CreateFont(-fa.nSize,0,0,0,fa.bBold?FW_BOLD:0,fa.bItalic,fa.bUnderline,fa.bStrikeout,0,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,DEFAULT_PITCH|FF_DONTCARE,&fa.szName[0]);
					CFont *pOldFont = lineDC.SelectObject(&font);
					CRect rect(0,0,ga.nMaxWidth,ga.nMaxHeight);
					lineDC.DrawText(" ",rect,DT_CALCRECT);
					lineDC.SelectObject(pOldFont);
					nCurVPos += rect.Height();
				} else
				{
					if(sCurElement != "")
						Parse_AppendText(&lineDC,&hover_lineDC,&nCurHPos,&nCurVPos,&nCurLineHeight,&arcLineRects,&general_attribs_tree.GetTail(),&font_attribs_tree.GetTail(),sCurElement,sCurLink,link);
					Parse_VAlignLine(&normalDC,&hotDC,&lineDC,&hover_lineDC,nCurHPos,nCurVPos,nCurLineHeight,&arcLineRects,&general_attribs_tree.GetTail());
					nCurVPos += nCurLineHeight;
					bIsLineEmpty = TRUE;
				}
				arcLineRects.RemoveAll();
				nCurLineHeight = 0;
				nCurHPos = 0;
				sCurElement = "";
				continue;
			}
			sCurElement += sData[i];
			bIsLineEmpty = FALSE;
		} else
		{
			if(sData[i] == '>')
			{
				if(sCurTagName == "font") // <font face="s" size="n" style="[-b|b][-i|i][-u|u][-s|s]" color="n,n,n" background="n,n,n">
				{
					font_attribs_tree.AddTail(fa);
					general_attribs_tree.AddTail(ga);
				} else if(sCurTagName == "" && sCurElement == "/font") // closing font tag.. revove the last attributes from the lists
				{
					if(font_attribs_tree.GetCount() > 1)
						font_attribs_tree.RemoveTail();
					if(general_attribs_tree.GetCount() > 1)
						general_attribs_tree.RemoveTail();
				} else if(sCurTagName == "" && sCurElement == "hr") // no parameters specified for the hr tag.. use the defaults
				{
					crHrColor = GetSysColor(COLOR_BTNSHADOW); // default color
					nHrWidth = ga.nMaxWidth-100; // default width
					nHrSize = 2;	// default height
					nHrAlign = 1;	// center by default
					sCurTagName = "hr";
				}
				if(sCurTagName == "hr") // wrap line is needed and draw rect
				{
					if(!bIsLineEmpty)
					{
						Parse_VAlignLine(&normalDC,&hotDC,&lineDC,&hover_lineDC,nCurHPos,nCurVPos,nCurLineHeight,&arcLineRects,&general_attribs_tree.GetTail());
						nCurVPos += nCurLineHeight;
						bIsLineEmpty = TRUE;
					}
					arcLineRects.RemoveAll();
					nCurLineHeight = 0;
					nCurHPos = 0;
					CRect rect;
					rect.left = nHrAlign == 0 ? 0 : (nHrAlign == 2 ? ga.nMaxWidth-nHrWidth : ga.nMaxWidth/2-nHrWidth/2);
					rect.right = rect.left + nHrWidth;
					rect.top = nCurVPos + 2;
					rect.bottom = rect.top + nHrSize;
					normalDC.FillSolidRect(rect,crHrColor);
					nCurVPos += 4+nHrSize;
				} else if(sCurTagName == "" && sCurElement== "/a" && sCurLink != "") // if we have an ending link tag AND valid link action and link region...
					sCurLink = "";
				else if(sCurTagName == "img" && sBitmap != "")
				{
					if(sBitmap[0]=='#') // only resource bitmaps allowed at this time
					{
						CBitmap bmp;
						bmp.LoadBitmap(atoi(sBitmap.Mid(1)));
						Parse_AppendBitmap(&lineDC,&hover_lineDC,&nCurHPos,&nCurVPos,&nCurLineHeight,&arcLineRects,&general_attribs_tree.GetTail(),&bmp, crBitmap, nBitmapBorder, sCurLink, link);
						bIsLineEmpty = FALSE;
					}
					crBitmap = CLR_NONE;
					nBitmapBorder = 0;
					sBitmap = "";
				} else if(sCurTagName == "br" || (sCurTagName == "" && sCurElement== "br")) // just substitute with newline character
				{
					sData.SetAt(i,'\n');
					i--;
				} else if(sCurTagName == "p" || (sCurTagName == "" && sCurElement== "p")) // just substitute with 2 newline characters
				{
					sData.SetAt(i,'\n');
					sData.SetAt(i-1,'\n');
					i-= 2;
				}
				sCurElement = "";
				bInsideTag = FALSE;
				continue;
			}
			if(sData[i] == ' ' && !bIsOption)
			{
				if(sCurElement != "")
				{
					if(sCurTagName == "")
					{
						sCurTagName = sCurElement;
						sCurTagName.MakeLower();
						if(sCurTagName == "font") // store latest font attributes. these are the ones that are modified by the font tags parameters
						{
							fa = font_attribs_tree.GetTail();
							ga = general_attribs_tree.GetTail();
						} else if(sCurTagName == "hr") // set default hr options...
						{
							crHrColor = GetSysColor(COLOR_BTNSHADOW);
							nHrWidth = ga.nMaxWidth-10;
							nHrSize = 2;
							nHrAlign = 1;
						} else if(sCurTagName == "a") // init link hot attributes
						{
							link = font_attribs_tree.GetTail();
							link.crColor = 0xeeffffff;
							link.crBkColor = 0xeeffffff;
							link.bBold = -10;
							link.bItalic = -10;
							link.bUnderline = -10;
							link.bStrikeout = -10;
							link.nSize = 0;
							link.szName[0] = '\0';
						} else if(sCurTagName == "img")
						{
							nBitmapBorder = 2;
							crBitmap = CLR_NONE;
							sBitmap = "";
						}
					} else
					{
						sCurOption = sCurTagName;
						sCurOption.MakeLower();
					}
				}
				sCurElement = "";
				continue;
			}
			if(sData[i] == '"' || sData[i] == '\'') // this happens when we have a new parameter value to parse
			{
				if(bIsOption && sData[i]==cTmp) // "sData[i]==cTmp" : closing (double)quote has to match opening quote
				{
					if(sCurTagName == "font") // parse font tag paramaters
					{
						if(sCurOption == "size") // font size
						{
							int nSize = atoi(sCurElement);
							if(nSize > 0 && nSize < 2000) // let's be reasonable
								fa.nSize = nSize;
						} else if(sCurOption == "face") // font face
						{
							strcpy(fa.szName,sCurElement.Left(MAX_PATH-1));
						} else if(sCurOption == "style") // font style (bold (b) ,italic (i) ,underline (u) ,strikeout (s) )
						{
							if(sCurElement.Find("-b")!=-1 || sCurElement.Find("-B")!=-1)
								fa.bBold = FALSE;
							else if(sCurElement.FindOneOf("bB")!=-1)
								fa.bBold = TRUE;
							if(sCurElement.Find("-i")!=-1 || sCurElement.Find("-I")!=-1)
								fa.bItalic = FALSE;
							else if(sCurElement.FindOneOf("iI")!=-1)
								fa.bItalic = TRUE;
							if(sCurElement.Find("-u")!=-1 || sCurElement.Find("-U")!=-1)
								fa.bUnderline = FALSE;
							else if(sCurElement.FindOneOf("uU")!=-1)
								fa.bUnderline = TRUE;
							if(sCurElement.Find("-s")!=-1 || sCurElement.Find("-S")!=-1)
								fa.bStrikeout = FALSE;
							else if(sCurElement.FindOneOf("sS")!=-1)
								fa.bStrikeout = TRUE;
						} else if(sCurOption == "color") // font color
							StringToColor(sCurElement,fa.crColor);
						else if(sCurOption == "background") // font background-color
							StringToColor(sCurElement,fa.crBkColor);
						else if(sCurOption == "align") // horisontal font alignment. here we change the "general_attribs"
						{					// only the latest open font tag with this parameter takes effect at a line break!!
							sCurElement.MakeLower();
							if(sCurElement == "left")
								ga.nAlign = 0;
							else if(sCurElement == "center")
								ga.nAlign = 1;
							else if(sCurElement == "right")
								ga.nAlign = 2;
						} else if(sCurOption == "valign") // vertical font alignment. here we change the "general_attribs"
						{
							sCurElement.MakeLower();
							if(sCurElement == "top")
								ga.nVAlign = 0;
							else if(sCurElement == "middle")
								ga.nVAlign = 1;
							else if(sCurElement == "bottom")
								ga.nVAlign = 2;
						}
					} else if(sCurTagName == "a")
					{
						if(sCurOption == "href") // what to do
							sCurLink = sCurElement;
						else if(sCurOption == "size") // font size
						{
							int nSize = atoi(sCurElement);
							if(nSize > 0 && nSize < 2000) // let's be reasonable
								link.nSize = nSize;
						} else if(sCurOption == "face") // font face
						{
							strcpy(link.szName,sCurElement.Left(MAX_PATH-1));
						} else if(sCurOption == "style") // font style (bold (b) ,italic (i) ,underline (u) ,strikeout (s) )
						{
							if(sCurElement.Find("-b")!=-1 || sCurElement.Find("-B")!=-1)
								link.bBold = FALSE;
							else if(sCurElement.FindOneOf("bB")!=-1)
								link.bBold = TRUE;
							if(sCurElement.Find("-i")!=-1 || sCurElement.Find("-I")!=-1)
								link.bItalic = FALSE;
							else if(sCurElement.FindOneOf("iI")!=-1)
								link.bItalic = TRUE;
							if(sCurElement.Find("-u")!=-1 || sCurElement.Find("-U")!=-1)
								link.bUnderline = FALSE;
							else if(sCurElement.FindOneOf("uU")!=-1)
								link.bUnderline = TRUE;
							if(sCurElement.Find("-s")!=-1 || sCurElement.Find("-S")!=-1)
								link.bStrikeout = FALSE;
							else if(sCurElement.FindOneOf("sS")!=-1)
								link.bStrikeout = TRUE;
						} else if(sCurOption == "color") // font color
							StringToColor(sCurElement,link.crColor);
						else if(sCurOption == "background") // font background-color
							StringToColor(sCurElement,link.crBkColor);
					} else if(sCurTagName == "img") // image tag: <img src="#resourceID">
					{
						// TODO: alow usage of filenames in <img> tag
						if(sCurOption == "src" && sCurElement != "")
							sBitmap = sCurElement;
						if(sCurOption == "color")
							StringToColor(sCurElement,crBitmap);
						if(sCurOption == "border" && sCurElement != "")
							nBitmapBorder = atoi(sCurElement);
					} else if(sCurTagName == "hr") // horisontal ruler
					{
						if(sCurElement != "")
						{
							if(sCurOption == "color") // color
								StringToColor(sCurElement,crHrColor);
							else if(sCurOption == "width") // width
								nHrWidth = atoi(sCurElement);
							else if(sCurOption == "size") // height
								nHrSize = atoi(sCurElement);
							else if(sCurOption == "align") // horz alignment
							{
								sCurElement.MakeLower();
								if(sCurElement=="left")
									nHrAlign = 0;
								else if(sCurElement=="right")
									nHrAlign = 2;
								else
									nHrAlign = 1;
							}
						}
					} else if((sCurTagName == "vspace") && (sCurOption == "size")) // vertical space
					{
						if(!bIsLineEmpty) // insert linebreak only is line isn't empty
						{
							Parse_VAlignLine(&normalDC,&hotDC,&lineDC,&hover_lineDC,nCurHPos,nCurVPos,nCurLineHeight,&arcLineRects,&general_attribs_tree.GetTail());
							nCurVPos += nCurLineHeight;
							bIsLineEmpty = TRUE;
						}
						arcLineRects.RemoveAll();
						nCurLineHeight = 0;
						nCurHPos = 0;
						nCurVPos += atoi(sCurElement); // add "size" parameters value to vertical offset
					} else if((sCurTagName == "hspace") && (sCurOption == "size")) // horisontal space
						nCurHPos += atoi(sCurElement); // add "size" parameters value to horisontal offset
					sCurElement = "";
					bIsOption = FALSE;
				} else if(sData[i-1] == '=') // parameter is beginning
				{
					sCurOption = sCurElement;
					sCurOption = sCurOption.Left(sCurOption.GetLength()-1); // remove trailing "=";
					sCurOption.MakeLower();
					sCurOption.TrimRight();
					sCurElement = "";
					cTmp = sData[i];
					bIsOption = TRUE;
				}
				continue;
			}
			sCurElement += sData[i]; // append non-formatting-significant character to curent element
		}

	}
	lineDC.SelectObject(pOldBmp);
	hover_lineDC.SelectObject(pOldHoverBmp);
	//... finished parsing

	m_nBitmapHeight = nCurVPos;

	// clean up
	normalDC.RestoreDC(nSaveDCNormal);
	hotDC.RestoreDC(nSaveDCHot);
}

void CCreditsCtrl::DrawBackground(CDC *pDC, RECT rect, BOOL bAnimate, DWORD lParam)
{
	pDC->FillSolidRect(&rect,(COLORREF)lParam);
}

void CCreditsCtrl::SetDefaultBkColor(COLORREF crColor)
{
	m_dwBackgroundPaintLParam = (DWORD)crColor; // default background color	
}

void CCreditsCtrl::Parse_AppendText(CDC *pDC, CDC *pHoverDC, int *pnCurHPos, int *pnCurVPos, int *pnCurHeight, CArray<line_rect,line_rect&>* parcLineRects, general_attribs *pga, font_attribs *pfa, CString sText, CString sCurLink, font_attribs link)
{
	CRect rect(0,0,pga->nMaxWidth,pga->nMaxHeight);
	CDC dc,hoverDC;
	CBitmap hoverBmp,bmp,*pOldHBmp;
	dc.CreateCompatibleDC(pDC);

	CFont font,hover_font,*pOldHFont;
	font.CreateFont(-pfa->nSize,0,0,0,pfa->bBold?FW_BOLD:0,pfa->bItalic,pfa->bUnderline,pfa->bStrikeout,0,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,DEFAULT_PITCH|FF_DONTCARE,pfa->szName);
	CFont *pOldFont = dc.SelectObject(&font);
	dc.SetTextColor(pfa->crColor == CLR_NONE ? m_crInternalTransparentColor : pfa->crColor);
	dc.SetBkColor(pfa->crBkColor == CLR_NONE ? m_crInternalTransparentColor : pfa->crBkColor);
	dc.SetBkMode(OPAQUE);
	
	dc.DrawText(sText,rect,DT_CALCRECT|DT_SINGLELINE);

	if(sCurLink != "")
	{
		if(link.bBold == -10) link.bBold = pfa->bBold;
		if(link.bItalic == -10) link.bItalic = pfa->bItalic;
		if(link.bUnderline == -10) link.bUnderline = pfa->bUnderline;
		if(link.bStrikeout == -10) link.bStrikeout = pfa->bStrikeout;
		if(link.crColor == 0xeeffffff) link.crColor = pfa->crColor;
		if(link.crBkColor == 0xeeffffff) link.crBkColor = pfa->crBkColor;
		if(link.nSize == 0) link.nSize = pfa->nSize;
		if(link.szName[0] == '\0') strcpy(link.szName,pfa->szName);

		CRect rect2(0,0,pga->nMaxWidth,pga->nMaxHeight);

		hoverDC.CreateCompatibleDC(pDC);

		hover_font.CreateFont(-link.nSize,0,0,0,link.bBold?FW_BOLD:0,link.bItalic,link.bUnderline,link.bStrikeout,0,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,PROOF_QUALITY,DEFAULT_PITCH|FF_DONTCARE,link.szName);
		pOldHFont = hoverDC.SelectObject(&hover_font);

		hoverDC.DrawText(sText,rect2,DT_CALCRECT|DT_SINGLELINE);

		if(rect.Width() < rect2.Width())
			rect = rect2;

		hoverBmp.CreateCompatibleBitmap(pDC,rect.right,rect.bottom);
		pOldHBmp = hoverDC.SelectObject(&hoverBmp);

		hoverDC.FillSolidRect(rect,m_crInternalTransparentColor);
		hoverDC.SetTextColor(link.crColor == CLR_NONE ? m_crInternalTransparentColor : link.crColor);
		hoverDC.SetBkColor(link.crBkColor == CLR_NONE ? m_crInternalTransparentColor : link.crBkColor);
		hoverDC.SetBkMode(OPAQUE);

		hoverDC.DrawText(sText,rect,DT_SINGLELINE);
	}
	
	bmp.CreateCompatibleBitmap(pDC,rect.right,rect.bottom);
	CBitmap *pOldBmp = dc.SelectObject(&bmp);

	dc.FillSolidRect(rect,m_crInternalTransparentColor);
	dc.SetBkColor(pfa->crBkColor == CLR_NONE ? m_crInternalTransparentColor : pfa->crBkColor);
	dc.DrawText(sText,rect,DT_SINGLELINE);

	if(sCurLink != "")
	{
		Parse_AppendElement(pDC,pHoverDC,pnCurHPos,pnCurVPos,pnCurHeight,parcLineRects,pga,rect.Width(),rect.Height(),&dc,&hoverDC,sCurLink);
		hoverDC.SelectObject(pOldHBmp);
		hoverDC.SelectObject(pOldHFont);
	}
	else
		Parse_AppendElement(pDC,pHoverDC,pnCurHPos,pnCurVPos,pnCurHeight,parcLineRects,pga,rect.Width(),rect.Height(),&dc,&dc,sCurLink);

	// clean up
	dc.SelectObject(pOldBmp);
	dc.SelectObject(pOldFont);
}

void CCreditsCtrl::Parse_AppendBitmap(CDC *pDC, CDC *pHoverDC, int *pnCurHPos, int *pnCurVPos, int *pnCurHeight, CArray<line_rect,line_rect&>* parcLineRects, general_attribs *pga, CBitmap *pBitmap, COLORREF crBorder, int nBorder, CString sCurLink, font_attribs link)
{
	BITMAP bm;
	pBitmap->GetBitmap(&bm);
	
	CDC bmpDC;
	bmpDC.CreateCompatibleDC(pDC);
	CBitmap *pOldBmp1 = bmpDC.SelectObject(pBitmap);

	CDC dc;
	dc.CreateCompatibleDC(pDC);
	CBitmap bmp;
	bmp.CreateCompatibleBitmap(pDC,bm.bmWidth+nBorder*2,bm.bmHeight+nBorder*2);
	CBitmap *pOldBmp2 = dc.SelectObject(&bmp);
	dc.FillSolidRect(0,0,bm.bmWidth+nBorder*2,bm.bmHeight+nBorder*2,crBorder==CLR_NONE?m_crInternalTransparentColor:crBorder);
	dc.BitBlt(nBorder,nBorder,bm.bmWidth,bm.bmHeight,&bmpDC,0,0,SRCCOPY);

	if(sCurLink == "")
		Parse_AppendElement(pDC,pHoverDC,pnCurHPos,pnCurVPos,pnCurHeight,parcLineRects,pga,bm.bmWidth+nBorder*2,bm.bmHeight+nBorder*2,&dc,&dc,sCurLink);
	else
	{
		CDC hoverDC;
		hoverDC.CreateCompatibleDC(pDC);
		CBitmap bmp;
		bmp.CreateCompatibleBitmap(pDC,bm.bmWidth+nBorder*2,bm.bmHeight+nBorder*2);
		CBitmap *pOldBmp = hoverDC.SelectObject(&bmp);	
		hoverDC.FillSolidRect(0,0,bm.bmWidth+nBorder*2,bm.bmHeight+nBorder*2,link.crColor==CLR_NONE?m_crInternalTransparentColor:link.crColor==0xeeffffff?crBorder:link.crColor);
		hoverDC.BitBlt(nBorder,nBorder,bm.bmWidth,bm.bmHeight,&bmpDC,0,0,SRCCOPY);
		Parse_AppendElement(pDC,pHoverDC,pnCurHPos,pnCurVPos,pnCurHeight,parcLineRects,pga,bm.bmWidth+nBorder*2,bm.bmHeight+nBorder*2,&dc,&hoverDC,sCurLink);
		hoverDC.SelectObject(pOldBmp);
	}

	// clean up
	dc.SelectObject(pOldBmp2);
	bmpDC.SelectObject(pOldBmp1);
}

void CCreditsCtrl::Parse_AppendElement(CDC *pDC, CDC *pHoverDC, int *pnCurHPos, int *pnCurVPos, int *pnCurHeight, CArray<line_rect,line_rect&>* parcLineRects, general_attribs *pga, int nElementWidth, int nElementHeight, CDC *pElementDC, CDC *pHoverElementDC, CString sCurLink)
{
	if(*pnCurHeight < nElementHeight)
		*pnCurHeight = nElementHeight;
	
	CRect rect;
	rect.left = *pnCurHPos;
	rect.top = 0;
	rect.right = rect.left+nElementWidth;
	rect.bottom = nElementHeight;

	line_rect lr;
	lr.rcRect = rect;
	lr.nVAlign = pga->nVAlign;
	lr.sLink = sCurLink;
	parcLineRects->Add(lr);

	pDC->BitBlt(rect.left,rect.top,rect.Width(),rect.Height(),pElementDC,0,0,SRCCOPY);
	pHoverDC->BitBlt(rect.left,rect.top,rect.Width(),rect.Height(),pHoverElementDC,0,0,SRCCOPY);
	*pnCurHPos += nElementWidth;	
}

void CCreditsCtrl::Parse_VAlignLine(CDC *pDestDC, CDC *pHoverDestDC, CDC *pLineDC, CDC *pHoverLineDC, int nCurHPos, int nCurVPos, int nCurHeight, CArray<line_rect,line_rect&>* parcLineRects, general_attribs *pga)
{
	{
		CArray<line_rect,line_rect&> LinkElements;

		CRect rect;
		CDC memDC;
		memDC.CreateCompatibleDC(pDestDC);
		CBitmap memBmp;
		memBmp.CreateCompatibleBitmap(pDestDC,nCurHPos,nCurHeight);
		CBitmap *pOldBmp = memDC.SelectObject(&memBmp);
		memDC.FillSolidRect(0,0,nCurHPos,nCurHeight,m_crInternalTransparentColor);
		for(int i = 0; i < parcLineRects->GetSize(); i++)
		{
			rect.left = (*parcLineRects)[i].rcRect.left;
			// calculate elements vertical position
			if((*parcLineRects)[i].nVAlign == 0) // top align
				rect.top = 0;
			else if((*parcLineRects)[i].nVAlign == 1) // middle align
				rect.top = nCurHeight/2-(*parcLineRects)[i].rcRect.bottom/2;
			else // bottom align
				rect.top = nCurHeight - (*parcLineRects)[i].rcRect.bottom;
			rect.bottom = rect.top + (*parcLineRects)[i].rcRect.bottom;
			// don't touch horz alignment
			rect.left = (*parcLineRects)[i].rcRect.left;
			rect.right = (*parcLineRects)[i].rcRect.right;

			// draw element
			memDC.BitBlt(rect.left,rect.top,rect.Width(),rect.Height(),pLineDC,(*parcLineRects)[i].rcRect.left,(*parcLineRects)[i].rcRect.top,SRCCOPY);

			// add link to list(if necessary)
			if((*parcLineRects)[i].sLink != "")
			{
				line_rect lr;
				lr.sLink = (*parcLineRects)[i].sLink;
				lr.rcRect = rect;
				LinkElements.Add(lr);
			}
		}

		rect.top = nCurVPos;
		rect.bottom = rect.top + nCurHeight;
		if(pga->nAlign == 0) // left align
			rect.left = 0;
		else if(pga->nAlign == 1) // center align
			rect.left = pga->nMaxWidth/2 - nCurHPos/2;
		else // right align
			rect.left = pga->nMaxWidth - nCurHPos;
		rect.right = rect.left + nCurHPos;

		TransparentBlt(&memDC,pDestDC,m_crInternalTransparentColor,rect,CRect(0,0,rect.Width(),rect.Height()));
		memDC.SelectObject(pOldBmp);

		// calculate horisontal offset of links in list and add them to global list
		for(i = 0; i < LinkElements.GetSize(); i++)
		{
			CRect rc = LinkElements[i].rcRect;
			rc.OffsetRect(rect.left,nCurVPos);
			m_HotRects.Add(rc);
			m_HotRectActions.Add(LinkElements[i].sLink);
		}
	}

	// do the same, but this time for the hover CD
	{
		CRect rect;
		CDC memDC;
		memDC.CreateCompatibleDC(pHoverDestDC);
		CBitmap memBmp;
		memBmp.CreateCompatibleBitmap(pHoverDestDC,nCurHPos,nCurHeight);
		CBitmap *pOldBmp = memDC.SelectObject(&memBmp);
		memDC.FillSolidRect(0,0,nCurHPos,nCurHeight,m_crInternalTransparentColor);
		for(int i = 0; i < parcLineRects->GetSize(); i++)
		{
			rect.left = (*parcLineRects)[i].rcRect.left;
			// calculate elements vertical position
			if((*parcLineRects)[i].nVAlign == 0) // top align
				rect.top = 0;
			else if((*parcLineRects)[i].nVAlign == 1) // middle align
				rect.top = nCurHeight/2-(*parcLineRects)[i].rcRect.bottom/2;
			else // bottom align
				rect.top = nCurHeight - (*parcLineRects)[i].rcRect.bottom;
			rect.bottom = rect.top + (*parcLineRects)[i].rcRect.bottom;
			// don't touch horz alignment
			rect.left = (*parcLineRects)[i].rcRect.left;
			rect.right = (*parcLineRects)[i].rcRect.right;

			// draw element
			memDC.BitBlt(rect.left,rect.top,rect.Width(),rect.Height(),pHoverLineDC,(*parcLineRects)[i].rcRect.left,(*parcLineRects)[i].rcRect.top,SRCCOPY);
		}

		rect.top = nCurVPos;
		rect.bottom = rect.top + nCurHeight;
		if(pga->nAlign == 0) // left align
			rect.left = 0;
		else if(pga->nAlign == 1) // center align
			rect.left = pga->nMaxWidth/2 - nCurHPos/2;
		else // right align
			rect.left = pga->nMaxWidth - nCurHPos;
		rect.right = rect.left + nCurHPos;

		TransparentBlt(&memDC,pHoverDestDC,m_crInternalTransparentColor,rect,CRect(0,0,rect.Width(),rect.Height()));
		memDC.SelectObject(pOldBmp);
	}
}

BOOL CCreditsCtrl::StringToColor(CString string, COLORREF &cr)
{
	int i,r,g,b;
	if(string=="")
		return FALSE;
	else if((string=="none")||(string=="transparant"))
		cr = CLR_NONE;
	else if((i = string.Find(','))==-1)
		return FALSE;
	else
	{
		r = atoi(string.Left(i));
		string.Delete(0,i+1);
		if((i = string.Find(','))==-1)
			return FALSE;
		else
		{
			g = atoi(string.Left(i));
			string.Delete(0,i+1);
			b = atoi(string);
			cr = RGB(r,g,b);
		}
	}
	return TRUE;
}

void CCreditsCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{
	if(m_bIsScrolling)
	{
		m_nCurBitmapOffset = m_nScrollStart-point.y;
		m_nCurBitmapOffset %= m_nBitmapHeight;
		if(m_nCurBitmapOffset < 0)
			m_nCurBitmapOffset = m_nCurBitmapOffset+m_nBitmapHeight;
		Invalidate(FALSE);
		UpdateWindow();
		return;
	}
	if(GetCapture()==this)
		ReleaseCapture();
	else
		SetCapture();

	if(m_rcClient.PtInRect(point))
	{
		int n;
		CRect rect;
		if((n = HitTest(point)) != -1)
		{
			rect = m_HotRects[n];
			SetCursor(m_hLinkCursor);
		}
		else
		{
			rect = CRect(0,0,0,0);
			SetCursor(m_hDefaultCursor);
		}
		if(rect != m_rcHotRect)
		{
			m_rcHotRect = rect;
//			Invalidate(FALSE);
//			UpdateWindow();
		}
	}
}

void CCreditsCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	int n = HitTest(point);
	if(n != -1)
	{
		m_rcHotRect = CRect(0,0,0,0); // will be update next timer tick
		CString s = m_HotRectActions[n];
		if(s[0] == '#')
		{
			int i = s.Find('#',1);
			CString arg = i==-1?"":s.Mid(1,i-1);
			s = s.Mid(i==-1?1:i+1);
			void(*func)(LPCTSTR) = (void(*)(LPCTSTR))atol(s);
			if(func)
				(*func)(i==-1?NULL:(LPCTSTR)arg);
		} else
			ShellExecute(NULL,NULL,s,NULL,NULL,SW_SHOW);
	} else if(m_bCanScroll && (m_nBitmapHeight > m_rcClient.bottom))
	{
		KillTimer(CCREDITCTRL_TIMER1);
		m_bIsScrolling = TRUE;
		m_nScrollStart = point.y + m_nCurBitmapOffset;
		Invalidate();
		UpdateWindow();
		SetCapture();
	}
}

void CCreditsCtrl::OnLButtonUp(UINT nFlags, CPoint point) 
{
	if(m_bIsScrolling)
	{
		ReleaseCapture();
		m_bIsScrolling = FALSE;
		OnTimer(CCREDITCTRL_TIMER1);
	}
}

int CCreditsCtrl::HitTest(CPoint pt)
{
	if(m_nBitmapHeight <= m_rcClient.bottom)
	{
		for(int i = 0; i < m_HotRects.GetSize(); i++)
		{
			if(m_HotRects[i].PtInRect(pt))
				return i;
		}
		return -1;
	}

	pt.y += m_nCurBitmapOffset;
	CPoint pt0 = pt;
	if(pt0.y > m_nBitmapHeight-m_nCurBitmapOffset)
		pt0.y -= m_nBitmapHeight;

	for(int i = 0; i < m_HotRects.GetSize(); i++)
	{
		if(m_HotRects[i].PtInRect(pt) || m_HotRects[i].PtInRect(pt0))
			return i;
	}
	return -1;
}